#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import functools
import os
import re
import uuid
from typing import Callable, List, Union

from common.const import RepositoryDataTypeEnum
from common.logger import Logger
from common.util.cmd_utils import get_livemount_path
from db2.comm.db2_exception import ErrCodeException

LOGGER = Logger().get_logger(filename="db2.log")


def handle_ex_return_default_value(default_return_value=None):
    """
    处理函数执行异常返回指定值
    @param default_return_value: 函数执行异常返回值
    """

    def decorator(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception:
                LOGGER.exception(f"Execute function failed, function name: {func.__name__}.")
                return default_return_value

        return inner

    return decorator


def adapt_paths(func: Callable[..., List[Union[str, List[str]]]]) -> Callable[..., List[Union[str, List[str]]]]:
    """
    装饰器，用于根据 repo_type 对返回的路径进行适配。

    当 repo_type 为 数据仓、日志仓、meta仓 时，对路径应用 decra_fun 进行适配。
    """

    @functools.wraps(func)
    def wrapper(*args, **kwargs) -> List[Union[str, List[str]]]:
        # 获取函数的返回值
        result = func(*args, **kwargs)

        # 尝试从 args 和 kwargs 中获取 repo_type
        # 假设 repo_type 是被装饰函数的关键字参数或第二个位置参数
        repo_type = ""

        # 如果 repo_type 是关键字参数
        if 'repo_type' in kwargs:
            repo_type = kwargs['repo_type']
        else:
            # 假设 repo_type 是第二个位置参数
            # 注意：args 的索引从 0 开始，因此第二个参数索引为 1
            if len(args) > 1:
                repo_type = args[1]
        # 定义需要适配的 adapt_repo_types
        adapt_repo_types = {RepositoryDataTypeEnum.DATA_REPOSITORY.value, RepositoryDataTypeEnum.LOG_REPOSITORY.value,
                            RepositoryDataTypeEnum.META_REPOSITORY.value}

        if repo_type not in adapt_repo_types:
            # 不需要适配，直接返回原结果
            return result

        # 如果 job_id 是关键字参数
        job_id = ""
        if 'job_id' in kwargs:
            job_id = kwargs['job_id']
        else:
            # job_id 是第三个位置参数
            # 注意：args 的索引从 0 开始，因此第三个参数索引为 2
            if len(args) > 2:
                job_id = args[2]

        # 检查返回值是否是嵌套列表
        if all(isinstance(item, list) for item in result):
            # 返回值是列表的列表
            adapted_result = [[get_livemount_path(job_id, p) for p in sublist] for sublist in result]
        else:
            # 返回值是平铺的列表
            adapted_result = [get_livemount_path(job_id, p) for p in result]

        return adapted_result

    return wrapper


def extract_from_part(input_str: str) -> str:
    """
    提取 from 后和 taken at 前的部分。

    参数:
        input_str (str): 输入的字符串。

    返回:
        str: 提取的 FROM 后面的部分，如果没有匹配则返回空字符串。
    """
    match = re.search(r"[from, FROM]\s+(.+?)\s+taken\s+at", input_str, re.IGNORECASE)
    if match:
        return match.group(1)
    return ""


def replace_from_part(input_str: str, new_from_value: str) -> str:
    """
    替换 from 后到 taken at 前的部分。

    参数:
        input_str (str): 输入的字符串。
        new_from_value (str): 用于替换的新的 FROM 部分。

    返回:
        str: 更新后的字符串。
    """
    pattern = r"(?i)(from\s+)(.*?)(\s+taken\s+at)"

    # 使用 re.sub 进行替换
    updated_str = re.sub(pattern, lambda m: f"{m.group(1)}{new_from_value}{m.group(3)}", input_str)

    return updated_str


def handle_restore_error(func):
    """
       装饰器函数，用于处理恢复命令执行时的异常。
       当恢复命令执行出错时，报SQL2543N异常会执行以下操作：
       1. 提取命令中的副本路径并创建一个唯一的软连接。
       2. 替换恢复命令中的路径部分为软连接的路径。
       3. 使用替换后的命令继续执行原方法。
       4. 执行完方法后，清除创建的软连接。

       参数:
           func (function): 被装饰的函数，即执行恢复操作的函数。

       返回:
           function: 包装后的函数，包含错误处理和软连接创建的功能。
    """

    @functools.wraps(func)
    def wrapper(cmd, *args, **kwargs):
        # 使用UUID生成唯一的路径
        uuid_str = str(uuid.uuid4())
        symlink_target = f"/mnt/{uuid_str}"

        try:
            # 执行原函数
            return func(cmd, *args, **kwargs)
        except ErrCodeException as e:
            LOGGER.error(f"Error occurred while executing restore command: {e}")
            if "SQL2543N" not in e.error_message:
                raise e
            # 提取路径
            from_part = extract_from_part(cmd)
            if not from_part:
                LOGGER.error("No valid FROM part found in the command.")
                raise e

            # 创建软连接，并赋予777权限
            try:
                # 删除已存在的软连接（如果有）
                if os.path.islink(symlink_target) or os.path.exists(symlink_target):
                    os.remove(symlink_target)
                # 创建新的软连接
                os.symlink(from_part, symlink_target)
                os.chmod(symlink_target, 0o777)  # 给与777权限
                LOGGER.info(f"Created symlink from {from_part} to {symlink_target} with 777 permissions")
            except Exception as link_error:
                LOGGER.error(f"Error occurred while creating symlink: {link_error}")
                raise Exception("Error occurred while creating symlink.")

            # 替换路径
            new_cmd = replace_from_part(cmd, symlink_target)
            LOGGER.info("Updated command:", new_cmd)

            # 执行替换后的命令
            try:
                return func(new_cmd, *args, **kwargs)
            finally:
                # 清理软连接
                if os.path.islink(symlink_target):
                    os.remove(symlink_target)
                    LOGGER.info(f"Cleaned up symlink {symlink_target}")

    return wrapper
